#include "timer.h"

#include <ratio>
#include <ctime>
#include <chrono>

using namespace std::chrono;

std::time_t getMilliSeconds() {
    auto now = system_clock::now();
    auto millionSecondsDuration = duration_cast<milliseconds>(now.time_since_epoch());
    return millionSecondsDuration.count();
}

TimerHandle Timer::addTimerOnce(TimerFunc timer_func, std::time_t duration, std::uint64_t user_data) {
    auto timerHandle = std::make_shared<TimerNode_>();
    timerHandle->timer_func = timer_func;
    timerHandle->type = Type_::ONCE;
    timerHandle->duration = duration;
    timerHandle->trigger_time = getMilliSeconds() + duration;
    timerHandle->user_data = user_data;
    queue_.emplace(timerHandle);
    return timerHandle;
}

Timer::~Timer() {    
}

TimerHandle Timer::addTimer(TimerFunc timer_func, std::time_t duration, std::uint64_t user_data) {
    auto timerHandle = std::make_shared<TimerNode_>();
    timerHandle->timer_func = timer_func;
    timerHandle->type = Type_::LOOP;
    timerHandle->duration = duration;
    timerHandle->trigger_time = getMilliSeconds() + duration;
    timerHandle->user_data = user_data;
    queue_.emplace(timerHandle);
    return timerHandle;
}

void Timer::cancelTimer(TimerHandle handle) {
    handle->deleted = true;
}

TimerHandle Timer::runOnce(std::time_t now) {
    auto handle = runOnce_(now);
    if (handle) {
        if (handle->timer_func) {
            handle->timer_func(handle->user_data);
        }
        return handle;
    } else {
        return nullptr;
    }
}

void Timer::clear() {
    while (!queue_.empty()) {
        queue_.pop();
    }
}

TimerHandle Timer::runOnce_(std::time_t now) {
    auto milli_seconds = now;
    if (!now) {
        milli_seconds = getMilliSeconds();
    }
    while (!queue_.empty()) {
        auto handle = queue_.top();
        if (handle->deleted) {
            queue_.pop();
            continue;
        }
        if (queue_.top()->trigger_time < milli_seconds) {
            queue_.pop();
            switch (handle->type) {
                case Type_::LOOP:
                    handle->trigger_time = milli_seconds + handle->duration;
                    queue_.push(handle);
                    break;
                default:
                    break;
            }
            return handle;
        } else {
            break;
        }
    }
    return nullptr;
}


